package com.gitee.service.openapi;

import com.gitee.gateway.core.utils.Configuration;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.FileSystemAccess;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.openapi.RouterBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Files;
import java.nio.file.Path;

/**
 * openapi server verticle
 */
public class HttpVerticle extends HttpVerticleBase {

    private final static Logger LOGGER = LoggerFactory.getLogger(HttpVerticle.class);

    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        //global router settings
        Router global = buildGlobalHandler(Router.router(vertx));

        JsonObject openapiConfig = config().getJsonObject("openapi");
        OpenApiHandler openApiHandler = loadOpenApiHandler(openapiConfig.getString("handler"));

        //openapi
        RouterBuilder.create(vertx, openapiConfig.getString("spec", "openapi.yaml")).compose(builder -> {
            JsonObject auths = openapiConfig.getJsonObject("auth");
            for (var auth : auths) {
                String authClassName = (String) auth.getValue();
                try {
                    TokenHandler tokenHandler = (TokenHandler) Class.forName(authClassName).getConstructor().newInstance();
                    builder.securityHandler(auth.getKey(), TokenAuthHandler.create(tokenHandler));
                    LOGGER.info("{} => {} loaded.", auth.getKey(), authClassName);
                } catch (Exception e) {
                    return Future.failedFuture(e);
                }
            }
            Router apiRouter = OperationsRouter.build(builder, openApiHandler);
            String path = openapiConfig.getString("path", "/api").trim();
            if (!path.endsWith("/"))
                path = STR."\{path}/";
            global.route(STR."\{path}*").subRouter(apiRouter);
            return Future.succeededFuture();
        }).compose(builder -> {
            //frontend static files and start http server
            String uri = openapiConfig.getString("swagger");
            if (StringUtils.isNotBlank(uri)) {
                if (!uri.endsWith("/"))
                    uri = STR."\{uri}/";
                this.buildSwaggerRouter(global, STR."\{uri}*");
            }
            return startHttpServer(global);
        }).onSuccess(server -> {
            boolean usingNative = vertx.isNativeTransportEnabled();
            LOGGER.info("READY ({}) {}!", server.actualPort(), usingNative ? "with Native Supported" : "");
            startPromise.complete();
        }).onFailure(startPromise::fail);
    }

    private OpenApiHandler loadOpenApiHandler(String className) throws Exception {
        return (OpenApiHandler) Class.forName(className).getConstructor().newInstance();
    }

    protected void buildSwaggerRouter(Router router, String uri) {
        Path path = Configuration.getRootPath().resolve("frontend/swagger");
        if (Files.exists(path) && Files.isDirectory(path)) {
            //swagger ui
            router.get(uri).handler(build(path.toString(), true));
        } else
            LOGGER.warn("Swagger directory '{}' is not exists or is not a directory: ", path);
    }

    protected StaticHandler build(String webroot, boolean cache) {
        return StaticHandler.create(FileSystemAccess.ROOT, webroot)
                .setAlwaysAsyncFS(true)
                .setCachingEnabled(cache)
                .setDefaultContentEncoding("UTF-8")
                .setEnableFSTuning(true)
                .setIncludeHidden(false)
                .setSendVaryHeader(true);
    }

}
